package com.googlecode.java_cl_parser;

import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

public class StringUtil
{
	private static final String PUNCTUATION = ".;:'\",`?/\\~!@#$%^&*()-=_+{}[]|<>";

	public static List<String> wrapTextToList(String text, int width)
	{
		if (width <= 1)
		{
			throw new IllegalArgumentException("Less than two-width is not supported");
		}

		List<String> list = new ArrayList<String>();

		List<String> wordList = splitWords(text);

		Stack<String> wordStack = new Stack<String>();

		// reverse order so item [0] comes out of the stack first
		Collections.reverse(wordList);

		wordStack.addAll(wordList);

		StringBuilder build = new StringBuilder();
		List<String> lineList = new ArrayList<String>();
		int currLength = 0;

		while (!wordStack.empty())
		{
			String pop = wordStack.pop();
			lineList.add(pop);
			currLength += pop.length();

			// check if we have exceeded the acceptable length
			int minLineLength = currLength + lineList.size() - 1;
			if (minLineLength == width)
			{
				// right on - push it and clear the line
				boolean first = true;
				for (String word : lineList)
				{
					if (first)
					{
						first = false;
					}
					else
					{
						build.append(' ');
					}

					build.append(word);
				}

				list.add(build.toString());

				lineList.clear();
				build.setLength(0);
				currLength = 0;
			}
			else if (minLineLength > width)
			{
				if (lineList.size() == 2)
				{
					// special case here - just chuck the second and pad to the end
					list.add(StringUtils.rightPad(lineList.get(0), width));
					wordStack.push(lineList.get(1));

					lineList.clear();
					build.setLength(0);
					currLength = 0;
				}
				else if (lineList.size() > 1)
				{
					// take one word off and pad the rest
					String lastWord = lineList.remove(lineList.size() - 1);
					wordStack.push(lastWord);

					currLength -= lastWord.length();

					int numGaps = lineList.size() - 1;

					int numSpaces = width - currLength;

					int numSpacesPerGap = numSpaces / numGaps;

					int remainder = numSpaces % numGaps;

					for (int wordNum = 0; wordNum < lineList.size(); wordNum++)
					{
						String word = lineList.get(wordNum);

						int numSpacesThisGap = numSpacesPerGap + (wordNum < remainder ? 1 : 0);

						build.append(word);

						if (wordNum < lineList.size() - 1)
						{
							for (int spaceNum = 0; spaceNum < numSpacesThisGap; spaceNum++)
							{
								build.append(' ');
							}
						}
					}

					list.add(build.toString());

					lineList.clear();
					build.setLength(0);
					currLength = 0;
				}
				else
				{
					// in this case we have a single word which is too big for one line.  Hyphenate and continue
					build.append(lineList.remove(0));

					String fragment = build.substring(0, width - 1) + '-';

					build.delete(0, width - 1);

					list.add(fragment);

					// push it back onto the stack so that it can be processed next, and clear the builder
					wordStack.push(build.toString());
					build.setLength(0);
					currLength = 0;
				}
			}
		}

		if (lineList.size() != 0)
		{
			boolean first = true;

			for (String word : lineList)
			{
				if (first)
				{
					first = false;
				}
				else
				{
					build.append(' ');
				}

				build.append(word);
			}
		}

		if (build.length() != 0 || list.size() == 0)
		{
			while (build.length() < width)
			{
				build.append(' ');
			}

			list.add(build.toString());
		}

		return list;
	}

	/**
	 * Does two things - strips all non-printable characters and splits on white space
	 *
	 * @param text
	 */
	public static List<String> splitWords(String text)
	{
		List<String> parts = new ArrayList<String>();

		StringBuilder stb = new StringBuilder();

		for (int offset = 0; offset < text.length(); offset++)
		{
			char ch = text.charAt(offset);

			boolean javaIdentifierPart = Character.isJavaIdentifierPart(ch);
			boolean whitespace = Character.isWhitespace(ch);
			boolean punctuation = PUNCTUATION.indexOf(ch) != -1;

			// ignore if not in our handled group
			if (javaIdentifierPart || whitespace || punctuation)
			{
				if (!whitespace)
				{
					stb.append(ch);
				}
				else
				{
					if (stb.length() != 0)
					{
						parts.add(stb.toString());
						stb.setLength(0);
					}
				}
			}
		}

		if (stb.length() != 0)
		{
			parts.add(stb.toString());
		}

		return parts;
	}
}
